﻿using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using Net.Event;
using Net.System;

namespace Net.Entities
{
    public class World
    {
        public string Name;
        public string Description;
        public FastList<Entity> Roots;
        private readonly TimerTick TimerTick;
        internal Queue<Action> EventList = new();
        public TimerEvent Event { get; set; } = new();

        public World()
        {
            Roots = new();
            TimerTick = new();
        }

        public World(string name) : this()
        {
            Name = name;
        }

        public void Init(params object[] args)
        {
            CollectAttributes();
            OnInit(args);
        }

        public virtual void OnInit(params object[] args) { }

        public void CollectAttributes()
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assemblie in assemblies)
            {
                Type[] types = null;
                try
                {
                    types = assemblie.GetTypes().Where(t => t.IsClass).ToArray();
                }
                catch (ReflectionTypeLoadException ex)
                {
                    foreach (Exception loadEx in ex.LoaderExceptions)
                    {
                        NDebug.LogError("Type load error: " + loadEx.Message);
                    }
                }
                if (types == null)
                    continue;
                foreach (var type in types)
                {
                    var staticMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
                    foreach (var staticMethod in staticMethods)
                    {
                        var attribute = staticMethod.GetCustomAttribute<RuntimeInitializeOnLoadMethodAttribute>();
                        if (attribute == null)
                            continue;
                        if (attribute.loadType == RuntimeInitializeLoadType.AfterSceneLoad)
                            staticMethod.Invoke(null, null);
                    }
                }
            }
        }

        public void AddEntity(Entity entity)
        {
            entity.World = this;
            Roots.Add(entity);
        }

        public void AddEntity(Entity entity, Entity parent)
        {
            entity.World = this;
            parent.Childs.Add(entity);
        }

        public void AddEntity(Component component)
        {
            Create(null, component);
        }

        public void Update() => OnUpdate();

        public void FixedUpdate(uint dt = 16, bool sleep = false)
        {
            var tick = (uint)Environment.TickCount;
            if (TimerTick.CheckTimeout(tick, dt, sleep))
                OnUpdate();
        }

        protected virtual void OnUpdate()
        {
            while (EventList.Count > 0)
                EventList.Dequeue().Invoke();
            for (int i = 0; i < Roots.Count; i++)
                Roots[i].OnUpdate();
            for (int i = 0; i < Roots.Count; i++)
                Roots[i].OnLateUpdate();
            Event.Update();
        }

        public Entity Create(string name, params Type[] components)
        {
            return Create(name, null, components);
        }

        public Entity Create(string name, params Component[] components)
        {
            return Create(name, null, components);
        }

        public Entity Create(string name, Entity parent, params Type[] componentTypes)
        {
            var components = new Component[componentTypes.Length];
            for (int i = 0; i < componentTypes.Length; i++)
                components[i] = Activator.CreateInstance(componentTypes[i]) as Component;
            return Create(name, parent, components);
        }

        public Entity Create(string name, Entity parent, params Component[] components)
        {
            var entity = new Entity
            {
                Name = name,
                World = this
            };
            if (parent == null)
                Roots.Add(entity);
            else
                parent.Childs.Add(entity);
            foreach (var component in components)
                entity.AddComponent(component);
            return entity;
        }

        public override string ToString()
        {
            return $"{Name}";
        }
    }
}
